﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using EmbeddedSharp.Core;
using Zove.Runtime.Core;
using Zove.Runtime.Core.Expression;
using Zove.Runtime.Core.Zoom;
using MyDictionary =
    System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, string>>;

namespace Ascend.Runtime.Core {
    public class AscendClassParser : IClassParser {
        /// <summary>
        /// REQUIRE
        /// </summary>
        public const string KEYWORD_REQUIRE = "REQUIRE";

        /// <summary>
        /// MODEL
        /// </summary>
        public const string KEYWORD_MODEL = "MODEL";

        /// <summary>
        /// MODEL
        /// </summary>
        public const string KEYWORD_ATOM = "ATOM";

        /// <summary>
        /// MODEL
        /// </summary>
        public const string KEYWORD_REFINES = "REFINES";

        /// <summary>
        /// END
        /// </summary>
        public const string KEYWORD_END = "END";

        /// <summary>
        /// content provider
        /// </summary>
        private readonly ITextContentProvider contentProvider;

        /// <summary>
        /// language nature
        /// </summary>
        protected ILanguageNature LanguageNature;

        /// <summary>
        /// Constructor with content provider, language nature
        /// </summary>
        /// <param name="contentProvider">content provider</param>
        /// <param name="languageNature">language nature</param>
        public AscendClassParser(ITextContentProvider contentProvider, ILanguageNature languageNature) {
            this.contentProvider = contentProvider;
            DataContext = new MyDictionary();
            AssertContext = new List<string>();
            LanguageNature = languageNature;
        }

        /// <summary>
        /// data context
        /// </summary>
        public MyDictionary DataContext { get; set; }

        /// <summary>
        /// assert context
        /// </summary>
        public List<string> AssertContext { get; set; }

        #region IClassParser Members

        /// <summary>
        /// Load from source
        /// </summary>
        /// <returns>ZoomClass</returns>
        public ZoomClass Parse() {
            string source = contentProvider.SourceReader.ReadToEnd();
            var lexer = new AscendLexer(source);

            var c = new ZoomClass();
            while (lexer.HasMoreTokens()) {
                Token token = lexer.NextToken(KEYWORD_MODEL, KEYWORD_REQUIRE);
                switch (token.Value)
                {

                    case KEYWORD_REQUIRE:
                        ParseRequire(lexer);
                        break;
                    case KEYWORD_MODEL:
                        c = ParseModel(lexer);
                        break;
                    case KEYWORD_ATOM:
                        ParseAtom(lexer);
                        break;
                    default:
                        throw new ParserException(token.Value);
                }
            }

            return c;
        }

        /// <summary>
        /// Parse an atom.
        /// 
        /// ATOM solver_var REFINES real DEFAULT 0.5 {?};
        ///
        ///   lower_bound IS_A real;
        ///   upper_bound IS_A real;
        ///   nominal IS_A real;
        ///
        ///   fixed IS_A boolean;
        ///   fixed := FALSE;
        ///   lower_bound := -1e20 {?};
        /// 
        ///   upper_bound := 1e20 {?};
        ///   nominal := 0.5 {?};
        ///
        /// END solver_var;
        /// </summary>
        /// <param name="lexer">the current lexer</param>
        public void ParseAtom(Lexer lexer)
        {
            Token token = lexer.NextToken(TokenType.IDENTIFIER); // the atom name
            token = lexer.NextToken(KEYWORD_REFINES);
            token = lexer.NextToken(TokenType.IDENTIFIER);

            token = lexer.NextToken();
            while (token.Value != "END")
            {
                token = lexer.NextToken();
            }
        }

        #endregion

        /// <summary>
        /// Parse model
        /// </summary>
        /// <param name="lexer"></param>
        /// <returns>a zoom object</returns>
        protected ZoomClass ParseModel(Lexer lexer) {
            var c = new ZoomClass();

            Token token = lexer.NextToken(TokenType.IDENTIFIER);
            c.Name = token.Value;
            c.Summary = lexer.LastComment;
            lexer.NextToken(TokenType.Semicolon);

            token = lexer.PeekToken();
            if (token.Value.Equals("REFINES")) {
                token = lexer.NextToken();
                ParseRefines(lexer, c);
            }

            Token nt = lexer.PeekToken();
            while (token.Value != "END") {
                if (token.Value == "METHODS") {
                    token = lexer.NextToken();
                    // do nothing
                } else if (token.Value == "METHOD") {
                    lexer.NextToken();
                    ParseMethod(lexer);
                } else if (token.TokenType == TokenType.IDENTIFIER
                           && (
                                  nt.TokenType == TokenType.IDENTIFIER
                                  || nt.TokenType == TokenType.Comma)) {
                    ParseDeclaration(lexer, c);
                } else {
                    ParseEquation(lexer, c);
                }

                token = lexer.PeekToken();
                nt = lexer.PeekToken(2);
            }
            lexer.NextToken(); // should be '}'

            token = lexer.NextToken(TokenType.IDENTIFIER);
            lexer.NextToken(TokenType.Semicolon);

            return c;
        }

        /// <summary>
        /// parse the method
        /// </summary>
        /// <param name="lexer">the current lexer</param>
        protected virtual void ParseMethod(Lexer lexer) {
            Token token = lexer.NextToken(TokenType.IDENTIFIER);
            string methodName = token.Value;
            lexer.NextToken(TokenType.Semicolon);

            token = lexer.NextToken();
            while (token.Value != "END") {
                if (token.Value == "FIX") {
                    string fix = lexer.ReadUntil(';');
                } else if (token.Value == "ASSERT") {
                    string expr = lexer.ReadUntil(';');
                    AssertContext.Add(expr);
                } else if (token.Value == "RUN") {
                    lexer.NextToken(TokenType.IDENTIFIER);
                    lexer.NextToken(TokenType.Semicolon);
                } else {
                    string key = token.Value;
                    lexer.NextToken(TokenType.Declare);

                    // TODO: an expression instead of a constant
                    token = lexer.NextToken(TokenType.Number);
                    string value = token.Value;
                    token = lexer.NextToken(TokenType.LeftBracket, TokenType.Semicolon);
                    if (token.TokenType == TokenType.LeftBracket) {
                        string unit = lexer.ReadUntil('}');
                        lexer.NextToken(TokenType.Semicolon);
                    }

                    if (!DataContext.ContainsKey(methodName)) {
                        DataContext.Add(methodName, new Dictionary<string, string>());
                    }

                    DataContext[methodName].Add(key, value);
                    Console.WriteLine(key + " := " + value);
                }

                token = lexer.NextToken();
            }

            token = lexer.NextToken(TokenType.IDENTIFIER);
            lexer.NextToken(TokenType.Semicolon);
        }

        /// <summary>
        /// Parse equatins into ASTs
        /// </summary>
        /// <param name="lexer">the lexer</param>
        /// <param name="c">the class object</param>
        protected virtual void ParseEquation(Lexer lexer, ZoomClass c) {
            int offset = lexer.Index;
            string ln = lexer.ReadUntil(';');
            if (ln.Contains(":==")) {
                // an assignmnt here
            } else {
                string label = string.Empty;

                if (ln.Contains(":")) {
                    label = ln.Split(':')[0];
                    ln = ln.Split(':')[1];
                }

                string[] p = ln.Split('=');

                #region analysis

                for (int i = 0; i < p.Length; i++) {
                    List<string> id = Utils.GetAllIdentifiers(p[i].Trim());

                    for (int j = i + 1; j < p.Length; j++) {
                        List<string> right = Utils.GetAllIdentifiers(p[j].Trim());
                        right.AddRange(id);

                        var e = new ZoomEquation();
                        e.LineNumber = lexer.LineNumber;
                        e.Parent = c;
                        e.Left = p[i].Trim();
                        e.Right = p[j].Trim();
                        e.Equation = e.Left + "=" + e.Right;

                        foreach (string m in right) {
                            string variableName = m;
                            if (m.Contains(".")) {
                                variableName = m.Split('.')[0];
                            }
                            //object constant = GetConstant(m);
                            //if (constant != null) {
                            //    e.Equation = Utils.ReplaceString(
                            //        m, constant.ToString(), e.Equation);
                            //} else {
                            //if (c.HasVariable(variableName)) {
                            if (!ZoomClassParser.HasElement(m, e)) {
                                var element = new ZoomElement();
                                element.Name = m;

                                e.Elements.Add(element);
                            }
                            //}
                        }
                        c.Equations.Add(e);
                    }
                }

                #endregion
            }
        }

        /// <summary>
        /// Parse a declaration
        /// </summary>
        /// <param name="lexer">the lexer</param>
        /// <param name="c">zoom class</param>
        protected void ParseDeclaration(Lexer lexer, ZoomClass c) {
            Token token;

            do {
                token = lexer.NextToken(
                    TokenType.IDENTIFIER, TokenType.Semicolon);
                if (token.TokenType == TokenType.Semicolon) {
                    break;
                }

                Dictionary<string, string> variableNames = new Dictionary<string, string>();
                variableNames.Add(token.Value, string.Empty);
                token = lexer.NextToken(TokenType.IDENTIFIER, TokenType.Comma);
                if (token.TokenType == TokenType.Comma) {
                    token = lexer.NextToken(TokenType.IDENTIFIER);

                    do
                    {
                        string variableName = token.Value;
                        variableNames.Add(variableName, string.Empty);
                        token = lexer.NextToken(
                            TokenType.IDENTIFIER, TokenType.Comma, TokenType.Quote);
                        switch (token.TokenType) {
                            case TokenType.IDENTIFIER:
                                // do nothing
                                break;
                            case TokenType.Comma:
                                token = lexer.NextToken(TokenType.IDENTIFIER);
                                break;
                            case TokenType.Quote:
                                StringBuilder buff = new StringBuilder("\"");

                                do
                                {
                                    token = lexer.NextToken();
                                    buff.Append(token.Value);
                                } while (token.TokenType != TokenType.Quote);
                                variableNames[variableName] = buff.ToString();
                                break;
                            default:
                                break;
                        }
                    } while (token.Value != "IS_A");
                }
                string define = token.Value;

                switch (define) {
                    case "IS_A":
                        ZoomVariable v;

                        token = lexer.NextToken(TokenType.IDENTIFIER);
                        string type = token.Value;
                        foreach (string name in variableNames.Keys) {
                            v = new ZoomVariable();
                            v.Parent = c;
                            v.Name = name;

                            v.Type = "factor"; // token.Value;
                            v.Summary = variableNames[name];
                            c.Variables.Add(v);
                        }
                        break;
                    case "ALIAS":
                        string aliasVariable = lexer.ReadUntil(';');
                        break;
                    case "METHODS":
                        break;
                    case "METHOD":
                        string methodName = token.Value;
                        token = lexer.NextToken();

                        while (token.Value != "end") {
                            switch (token.Value) {
                                case "FIX":
                                    break;
                                default:
                                    break;
                            }
                        }
                        break;
                    default:
                        break;
                }
            } while (lexer.HasMoreTokens());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="lexer"></param>
        /// <param name="c"></param>
        protected virtual void ParseRefines(Lexer lexer, ZoomClass c) {
            Token token = null;

            do {
                token = lexer.NextToken(TokenType.IDENTIFIER);
                c.BaseClasses.Add(token.Value);
                token = lexer.NextToken();
            } while (token.TokenType != TokenType.LeftBracket);
        }

        /// <summary>
        /// Parse the require files
        /// </summary>
        /// <param name="lexer">the current lexer</param>
        protected virtual void ParseRequire(Lexer lexer) {
            Token token = lexer.NextToken();
            var b = new StringBuilder();
            while (lexer.HasMoreTokens()
                   && token.TokenType != TokenType.Semicolon) {
                b.Append(token.Value);
                token = lexer.NextToken();
            }
            if (!lexer.HasMoreTokens()) {
                throw new UnexpectedEndOfSourceException("");
            }

            string path = b.ToString();
            if (!File.Exists(path)) {
                bool successfulMatched = false;
                foreach (string classPath in LanguageNature.ClassFinder.ClassPaths) {
                    string newFileName =
                        classPath.EndsWith("\\") ? classPath + path : classPath + "\\" + path;
                    if (File.Exists(newFileName)) {
                        successfulMatched = true;
                        path = newFileName;
                        break;
                    }
                }
                if (!successfulMatched) {
                    throw new FileNotFoundException("Cannot find the required file", path);
                }
            }
            LanguageNature.ClassFinder.AddClassPath(path);
        }
    }
}